/*
 *
 *
Copyright (c) 1998-2001 Aristar, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any,
must include the following acknowledgment:

"This product includes software developed by Aristar, Inc. (http://www.aristar.com/)."

Alternately, this acknowledgment may appear in the software itself, if and
wherever such third-party acknowledgments normally appear.

4. The names "Dialect" and "Aristar, Inc." must not be used to endorse or
promote products derived from this software without prior written permission.
For written permission, please contact info@aristar.com.

5. Products derived from this software may not be called "Dialect", nor may
"Dialect" appear in their name, without prior written permission of Aristar, Inc.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ARISTAR, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software consists of voluntary contributions made by many individuals
on behalf of Aristar, Inc.  For more information on Aristar, Inc.,
please see <http://www.aristar.com/>.

Portions of this software are based upon software freely available and
written by individuals not associated with Aristar, Inc.  Copyrights are
maintained in the relevant source code files.
 *
 *
 */

#include "stdafx.h"
#include "DConfigGlobals.h"
#include "Interpreter.h"
#include "DialectInterp.h"
#include "CoreFunctions.h"
#include "CImplementer.h"
#include "CRegExpression.h"

///////////////////////////////////
#if defined(USING_MFC) && defined(_DEBUG)
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////

//
//	Core functions are special because they are "real" function objects
//	and can therefore be passed as arguments to other functions (eg. map).
//	This makes them a little more useful than the functions implemented
//	in CInterpreter::ReadNumber(), but it also means they won't execute
//	as fast since they require at least one symbol table lookup, etc.
//	They are not defined in CTokenTypes.
//

#define XERR(x) pInterp->RuntimeError(_T(x))
#define ERR(x) {XERR(x);return AllocNil();}
#define REQUIRE_ARGS(x) if (argCount!=x) ERR("Wrong number of arguments to function.")
#define REQUIRE_STRING(x) if (args[x].Type() != stString) ERR("Expected string in argument(s) to function.")
#define REQUIRE_FLOAT(x) if ((args[x].Type() != stFloat)||(args[x].Type() != stInteger)) ERR("Expected number in argument(s) to function.")
#define REQUIRE_INTEGER(x) if ((args[x].Type() != stInteger)) ERR("Expected integer in arguments(s) to function.")
#define REQUIRE_ARRAY(x) if ((args[x].Type() != stArray)) ERR("Expected array in arguments(s) to function.")


///////////////////////////////////////////////////////////////////
// Platform dependant functions (message and inputbox)
///////////////////////////////////////////////////////////////////

VALUE message(VALUE* self, int argCount, VALUE args[], CInterpreter* pInterp);
VALUE inputbox(VALUE* self, int argCount, VALUE args[], CInterpreter* pInterp);

///////////////////////////////////////////////////////////////////
// Non-Platform dependant functions
///////////////////////////////////////////////////////////////////

static int parseFormat(LPTSTR& lpz, TString& format,
					   TCHAR& flags, int& width, int& precision, TCHAR& type)
{
	// Following the printf format spec
	// %[flags][width][.precision][{h|l|I64|L}]type
	// Where the initial '%' has already been eaten
	ASSERT(lpz);

	format = _T("%");
	flags = '\0';
	width = -1;
	precision = -1;
	type = '\0';

	LPCTSTR lpzStart = lpz;

	switch (*lpz) {
		case '-': case '+': case '0': case ' ': case '#':
			flags = *lpz++;
			format += flags;
			break;
	}

	if (isdigit(*lpz)) {
		width = 0;
		while (isdigit(*lpz)) {
			width = (width * 10) + (*lpz - '0');
			format += *lpz++;
		}
	}

	if (*lpz == '.') {
		precision = 0;
		++lpz;
		format += '.';
		while (isdigit(*lpz)) {
			precision = (precision * 10) + (*lpz - '0');
			format += *lpz++;
		}
	}

	switch (*lpz) {
		case 'h': case 'l': case 'L':
			format += *lpz++;
			break;
		case 'I':
			if ((*(lpz + 1) == '6') && (*(lpz + 1) == '6')) {
				format += _T("I64");
				lpz += 3;
			}
			break;
	}

	type = *lpz++;
	format += type;

	return (lpz - lpzStart) / sizeof(TCHAR);
}

CORE_FUNC(format)
{
	// Following the printf format spec
	// %[flags][width][.precision][{h|l|I64|L}]type
	//LPCTSTR szRegex = _T("^([-+0 #]?)(\\d*)(\\.\d+)(h|l|I64|L)([a-zA-Z])");
	//CRegExpression regex;
	//regex.Compile(szRegex, 0);

	//
	// No flags are currently supported
	//
	// Types supported:
	//		d: integer
	//		x: hexadecimal
	//		f: float
	//		s: string

	if (argCount < 1) {
		ERR("Invalid argument count");
	}

	TString str = args[0].String();
	LPTSTR lpz = (LPTSTR)(LPCTSTR)str;
	TString newStr, format;
	int nextArg = 1;

	while (*lpz) {
		if (*lpz != '%') {
			newStr += *lpz++;
		}
		else {
			++lpz;
			if (*lpz == '\0') break;
			else if (*lpz == '%') {
				++lpz;
				newStr += '%';
			}
			else {
				// We have a format thingy
				if (nextArg >= argCount) {
					ERR("Not enough arguments for all formats");
				}
				int width, precision;
				TCHAR flags, type, buf[128];
				*buf = '\0';
				parseFormat(lpz, format, flags, width, precision, type);
				switch (type) {
					case 'i':
					case 'd':
					case 'x':
					case 'X':
						wsprintf(buf, format, args[nextArg++].Integer());
						break;
					case 'f':
						_stprintf(buf, format, args[nextArg++].Float());
						break;
					case 's':
						if (precision == -1) {
							newStr += args[nextArg++].String();
						}
						else {
							newStr += args[nextArg++].String().Left(precision);
						}
						break;
					default:
						ERR("Unsupported format type");
				}
				if (*buf) newStr += buf;
			}
		}
	}
	return newStr;
}

CORE_FUNC(abortProgram)
{
	pInterp->Abort();
	return AllocNil();
}

CORE_FUNC(asc)
{
	if ((argCount != 1) || (args[0].Type() != stString) || (args[0].String().GetLength() == 0)) {
		ERR("asc() takes one single-character string argument");
	}
	return (int)(unsigned char)(args[0].String()[0]);
}

CORE_FUNC(mid_)
{
	if ((argCount != 2) && (argCount != 3)) {
		pInterp->RuntimeError(_T("mid$() takes two or three arguments"));
		return AllocNil();
	}

	TString str = args[0].String();
	int strLen = str.GetLength();

	int first = args[1].Integer() - 1;
	if ((first < 0) || (first >= strLen)) {
		//pInterp->RuntimeError(_T("mid$() first index out-of-bounds"));
		//return AllocNil();
		return _T("");
	}

	int count, maxCount = strLen - first;
	if (argCount == 3) {
		count = args[2].Integer();
		if (count > maxCount) count = maxCount;
	}
	else {
		count = maxCount;
	}

	return str.Mid(first, count);
}

CORE_FUNC(trim_)
{
	if (argCount != 1) {
		pInterp->RuntimeError(_T("trim$() takes exactly one argument"));
		return AllocNil();
	}

	TString result(args[0].String());
	result.TrimRight();
	result.TrimLeft();
	return result;
}

CORE_FUNC(split)
{
	if ((argCount > 3) || (argCount < 1)) {
		pInterp->SyntaxError(_T("split() takes one to three arguments"));
		return AllocNil();
	}

	TString splitAt = _T(" ");	// Default split at single space
	bool bCombineSplit;
	TString string = args[0].String();

	if (argCount == 1) {
		bCombineSplit = false;
	}
	else if (argCount == 2) {
		splitAt = args[1].String();
		bCombineSplit = false;
	}
	else if (argCount == 3) {
		splitAt = args[1].String();
		bCombineSplit = args[2].Boolean();
	}

	if (splitAt.IsEmpty()) {
		// Special case
		int length = string.GetLength();
		VALUE v = AllocArray(length);
		for (int idx = 0; idx < length; ++idx) {
			v.Set(idx + 1, TString(string[idx]));
		}
		return v;
	}

	int length = 0;
	VALUE vResult = AllocArray(0);
	int pos = string.Find(splitAt);
	while (pos >= 0) {
		if ((string.GetLength() == 0) || (pos < 0)) break;

		vResult.Resize(++length);
		vResult.Set(length, string.Left(pos));
		if (string.Mid(pos) == splitAt) {
			// This is the end and the term was the last item
			// ex.  split("1/2/", "/")  ->  ["1", "2", ""]
			// message(split("/1//2/", "/", true))
			// **** This next one doesn't work correctly!!!
			// message(split("ababab1ababab2ababab", "ab", true))
			vResult.Resize(++length);
			vResult.Set(length, _T(""));
		}
		string = string.Mid(pos + splitAt.GetLength());
		while (bCombineSplit && (string.Left(splitAt.GetLength()) == splitAt)) {
			string = string.Mid(splitAt.GetLength());
			pos = string.Find(splitAt);
		}
		pos = string.Find(splitAt);
	}
	if (string.GetLength() > 0) {
		vResult.Resize(++length);
		vResult.Set(length, string);
	}
	return vResult;
}

CORE_FUNC(array)
{
	if ((argCount > 2) || (args[0].Type() != stInteger)) {
		pInterp->SyntaxError(_T("Invalid args passed to array()"));
		return AllocNil();
	}

	VALUE result;
	if (argCount == 1) result = AllocArray(args[0].Integer(), AllocNil());
	else result = AllocArray(args[0].Integer(), args[1]);

	return result;
}

CORE_FUNC(resize)
{
	if ((argCount != 2) || (args[1].Type() != stInteger)) {
		pInterp->SyntaxError(_T("resize() takes one array and one integer"));
		return AllocNil();
	}

	switch (args[0].Type()) {
		case stChunk:
		case stArray:	args[0].Resize(args[1].Integer(), AllocNil()); break;
		case stNil:		break;
		default:
			pInterp->SyntaxError(_T("Type can't be resized: ") + args[0].TypeString());
			return AllocNil();
	}
	return args[0];
}

CORE_FUNC(clone)
{
	if (argCount != 1) {
		pInterp->SyntaxError(_T("clone() requires one arg"));
		return AllocNil();
	}
	return args[0].Clone();
}

CORE_FUNC(deepclone)
{
	if (argCount != 1) {
		pInterp->SyntaxError(_T("deepclone() requires one arg"));
		return AllocNil();
	}
	return args[0].DeepClone();
}

CORE_FUNC(callstack)
{
	if (argCount != 0) {
		pInterp->SyntaxError(_T("callStack takes zero args"));
		return AllocNil();
	}
	return pInterp->CallStack();
}

CORE_FUNC(makeexe)
{
	if ((argCount != 4) || (args[0].Type() != stString) ||
		(args[1].Type() != stString) || (args[2].Type() != stString) ||
		(args[3].Type() != stString))
	{
		ERR("makeExe takes four string args <outputFile, interpStubEXE, sourceDir, initialScript>");
	}

	return ((CDialectInterp*)pInterp)->MakeExe(args[0].String(), args[1].String(), args[2].String(),
		args[3].String());
}

CORE_FUNC(match)
{
	if (argCount < 2) {
		ERR("match function expects at least two arguments");
	}

	REQUIRE_STRING(0);
	REQUIRE_ARRAY(1);

	int regMods = 0;
	if ((argCount == 3) && args[2].Boolean()) {
		// true == case insensitive matching...
		regMods = REG_ICASE;
	}

	CString text = args[0].String();
	VALUE vArray = args[1];
	int count = vArray.Length();

	if ((count > 0) && (vArray[1].Type() != stArray)) {
		ERR("Expecting an array of arrays in second arg to match()");
	}

	for (int idx = 1; idx <= count; ++idx) {
		CRegExpression regex;
		if (!regex.Compile(vArray[idx][1].String(), regMods)) {
			TString error;
			switch (regex.GetLastErrorCode()) {
				case REG_ESPACE:
				case REG_ASSERT:
				case REG_INVARG:
					pInterp->InternalError(_T("Error generating regex: %s") + vArray[idx][1].String());
					break;
				default:
					pInterp->RuntimeError(_T("Invalid regular expression pattern: ") + vArray[idx][1].String());
			}
			return AllocNil();
		}

		// offset >= 0 mean match found
		// offset == -1 means no match found, but otherwise no error
		// offset == -2 means we have a critical regexp error
		int offset = regex.Match(text);
		if (offset >= 0) {
			// We found our match
			return vArray[idx][2];
		}
		else if (offset == -2) {
			pInterp->RuntimeError(_T("Critical regex.match(): %s"), (LPCTSTR)regex.GetLastError());
			return AllocNil();
		}
	}
	return AllocNil();
}

CORE_FUNC(_int_)
{
	REQUIRE_ARGS(1);
	return args[0].Integer();
}

CORE_FUNC(_float_)
{
	REQUIRE_ARGS(1);
	return args[0].Float();
}

CORE_FUNC(unparse)
{
	REQUIRE_ARGS(1);
	return args[0].__unparse__();
}

CORE_FUNC(abs)
{
	REQUIRE_ARGS(1);
	switch (args[0].Type()) {
		case stInteger:
			return args[0].Integer() >= 0 ? args[0].Integer() : -args[0].Integer();
		case stFloat:
			return args[0].Float() >= 0 ? args[0].Float() : -args[0].Float();
		default:
			pInterp->RuntimeError(_T("Can't compute absolute value of this type: ") +
				args[0].TypeString());
			return AllocNil();
	}
}

CORE_FUNC(join)
{
	REQUIRE_ARGS(2);
	REQUIRE_ARRAY(0);
	TString result;
	int length = args[0].Length();
	for (int idx = 1; idx <= length; ++idx) {
		result += args[0][idx].String();
		if (idx != length) result += args[1].String();
	}
	return result;
}

CORE_FUNC(remove)
{
	REQUIRE_ARGS(2);
	args[0].Remove(args[1].String());
	return args[0];
}

///////////////////////////////////////////////////////////////////

static inline bool _Merge(VALUE array[], VALUE tempArray[], int lpos, int rpos, int rend,
						 VALUE vCompareFunc, CInterpreter* pInterp, VALUE vOption)
{
	int lend = rpos - 1;
	int tmppos = lpos;
	int numelements = rend - lpos + 1;

	VALUE vArgs[3];
	VALUE vCompResult;

	int argCount = (vOption.Type() == stUndefined) ? 2 : 3;
	vArgs[2] = vOption;

	while (lpos <= lend  && rpos <= rend) {
		vArgs[0] = array[lpos];
		vArgs[1] = array[rpos];
		if (!pInterp->ExecFuncFromCore(vCompResult, vCompareFunc, argCount, vArgs, NULL)) {
			return false;
		}

		if (vCompResult.Boolean()) {
			tempArray[tmppos++] = array[lpos++];
		}
		else {
			tempArray[tmppos++] = array[rpos++];
		}
	}

	while (lpos <= lend) tempArray[tmppos++] = array[lpos++];

	while (rpos <= rend) tempArray[tmppos++] = array[rpos++];

	for (int idx = 0; idx < numelements; ++idx, --rend) {
		array[rend] = tempArray[rend];
	}

	return true;
}

static bool _MergeSort(VALUE array[], VALUE tempArray[], int left, int right,
					   VALUE vCompareFunc, CInterpreter* pInterp, VALUE vOption)
{
	if (left < right) {
		int center = (left + right) >> 1;
		if (!_MergeSort(array, tempArray, left, center, vCompareFunc, pInterp, vOption) ||
			!_MergeSort(array, tempArray, center + 1, right, vCompareFunc, pInterp, vOption) ||
			!_Merge(array, tempArray, left, center + 1, right, vCompareFunc, pInterp, vOption))
		{
			return false;
		}
	}
	return true;
}

CORE_FUNC(_mergesort)
{
	// mergesort(array, comparefunc(v1, v2, <optional>), <optional>)
	if ((argCount < 2) || (args[0].Type() != stArray) || (args[1].Type() != stFunction)) {
		ERR("Invalid arguments to mergesort()");
	}

	VALUE vOption;
	if (argCount >= 3) {
		vOption = args[2];
	}

	VALUE vFunc = args[1];
	if (vFunc.Function()->m_ParamCount != argCount) {
		ERR("Compare function passed to mergesort must take two or three arguments");
	}

	VALUE vResult;
	CArrayImpl* array = args[0].Array();
	vResult = AllocArray(array->m_Length, array->m_Values);
	VALUE* tempArray = new VALUE[array->m_Length];
	// If an error occurs in the sort func, it will have already
	// set an exception state in pInterp, so don't overwrite it
	// even if _MergeSort returns false
	_MergeSort(vResult.Array()->m_Values, tempArray, 0, array->m_Length - 1, vFunc, pInterp, vOption);
	delete[] tempArray;
	return vResult;
}

#ifndef DIALECT_LITE
CORE_FUNC(CompileFile)
{
	if ((argCount != 2) || (args[0].Type() != stString) || (args[1].Type() != stString)) {
		ERR("Invalid arguments to compilefile(filenameIn, filenameOut)");
	}

	TString exception;
	if (!pInterp->CompileFile(args[0].String(), args[1].String(), exception)) {
		pInterp->RuntimeError(_T("compileFile() failed: ") + exception);
	}
	return AllocNil();
}
#endif

///////////////////////////////////////////////////////////////////

//==========================================================
// Some time and date functions

static LPCTSTR MonthsArray[12] = {
	_T("January"), _T("February"), _T("March"), _T("April"), _T("May"), _T("June"), _T("July"),
	_T("August"), _T("September"), _T("October"), _T("November"), _T("December")
};

static TString MonthOf(int month) {
	if ((month > 12) || (month < 1)) {
		RunTimeError(_T("Invalid month number: %d"), month);
		return _T("<Error>");
	}
	return MonthsArray[month - 1];
}

static TString YearOf(int year)
{
	TString result;
	if (year > 1800) year = year;
	else if (year > 25) year += 1900;
	else year += 2000;	// Semi- year 2000 compliant (up to 2025)
	result.Format(_T("%d"), year);
	return result;
}

#if defined(__WINNT__) || defined(__WINCE__)
#include <afxdisp.h>

#if UNDER_CE < 210
// COleDateTime::GetAsSystemTime didn't exist until CE 2.10
// We must make our own version (code taken from olevar.cpp)
#include <math.h>
// Verifies will fail if the needed buffer size is too large
#define MAX_TIME_BUFFER_SIZE    128         // matches that in timecore.cpp
#define MIN_DATE                (-657434L)  // about year 100
#define MAX_DATE                2958465L    // about year 9999
// Half a second, expressed in days
#define HALF_SECOND  (1.0/172800.0)
// One-based array of days in year at month start
static int _afxMonthDays[13] =
	{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};

static BOOL _AfxTmFromOleDate(DATE dtSrc, struct tm& tmDest)
{
	// The legal range does not actually span year 0 to 9999.
	if (dtSrc > MAX_DATE || dtSrc < MIN_DATE) // about year 100 to about 9999
		return FALSE;

	long nDays;             // Number of days since Dec. 30, 1899
	long nDaysAbsolute;     // Number of days since 1/1/0
	long nSecsInDay;        // Time in seconds since midnight
	long nMinutesInDay;     // Minutes in day

	long n400Years;         // Number of 400 year increments since 1/1/0
	long n400Century;       // Century within 400 year block (0,1,2 or 3)
	long n4Years;           // Number of 4 year increments since 1/1/0
	long n4Day;             // Day within 4 year block
							//  (0 is 1/1/yr1, 1460 is 12/31/yr4)
	long n4Yr;              // Year within 4 year block (0,1,2 or 3)
	BOOL bLeap4 = TRUE;     // TRUE if 4 year block includes leap year

	double dblDate = dtSrc; // tempory serial date

	// If a valid date, then this conversion should not overflow
	nDays = (long)dblDate;

	// Round to the second
	dblDate += ((dtSrc > 0.0) ? HALF_SECOND : -HALF_SECOND);

	nDaysAbsolute = (long)dblDate + 693959L; // Add days from 1/1/0 to 12/30/1899

	dblDate = fabs(dblDate);
	nSecsInDay = (long)((dblDate - floor(dblDate)) * 86400.);

	// Calculate the day of week (sun=1, mon=2...)
	//   -1 because 1/1/0 is Sat.  +1 because we want 1-based
	tmDest.tm_wday = (int)((nDaysAbsolute - 1) % 7L) + 1;

	// Leap years every 4 yrs except centuries not multiples of 400.
	n400Years = (long)(nDaysAbsolute / 146097L);

	// Set nDaysAbsolute to day within 400-year block
	nDaysAbsolute %= 146097L;

	// -1 because first century has extra day
	n400Century = (long)((nDaysAbsolute - 1) / 36524L);

	// Non-leap century
	if (n400Century != 0)
	{
		// Set nDaysAbsolute to day within century
		nDaysAbsolute = (nDaysAbsolute - 1) % 36524L;

		// +1 because 1st 4 year increment has 1460 days
		n4Years = (long)((nDaysAbsolute + 1) / 1461L);

		if (n4Years != 0)
			n4Day = (long)((nDaysAbsolute + 1) % 1461L);
		else
		{
			bLeap4 = FALSE;
			n4Day = (long)nDaysAbsolute;
		}
	}
	else
	{
		// Leap century - not special case!
		n4Years = (long)(nDaysAbsolute / 1461L);
		n4Day = (long)(nDaysAbsolute % 1461L);
	}

	if (bLeap4)
	{
		// -1 because first year has 366 days
		n4Yr = (n4Day - 1) / 365;

		if (n4Yr != 0)
			n4Day = (n4Day - 1) % 365;
	}
	else
	{
		n4Yr = n4Day / 365;
		n4Day %= 365;
	}

	// n4Day is now 0-based day of year. Save 1-based day of year, year number
	tmDest.tm_yday = (int)n4Day + 1;
	tmDest.tm_year = n400Years * 400 + n400Century * 100 + n4Years * 4 + n4Yr;

	// Handle leap year: before, on, and after Feb. 29.
	if (n4Yr == 0 && bLeap4)
	{
		// Leap Year
		if (n4Day == 59)
		{
			/* Feb. 29 */
			tmDest.tm_mon = 2;
			tmDest.tm_mday = 29;
			goto DoTime;
		}

		// Pretend it's not a leap year for month/day comp.
		if (n4Day >= 60)
			--n4Day;
	}

	// Make n4DaY a 1-based day of non-leap year and compute
	//  month/day for everything but Feb. 29.
	++n4Day;

	// Month number always >= n/32, so save some loop time */
	for (tmDest.tm_mon = (n4Day >> 5) + 1;
		n4Day > _afxMonthDays[tmDest.tm_mon]; tmDest.tm_mon++);

	tmDest.tm_mday = (int)(n4Day - _afxMonthDays[tmDest.tm_mon-1]);

DoTime:
	if (nSecsInDay == 0)
		tmDest.tm_hour = tmDest.tm_min = tmDest.tm_sec = 0;
	else
	{
		tmDest.tm_sec = (int)nSecsInDay % 60L;
		nMinutesInDay = nSecsInDay / 60L;
		tmDest.tm_min = (int)nMinutesInDay % 60;
		tmDest.tm_hour = (int)nMinutesInDay / 60;
	}

	return TRUE;
}

BOOL __GetAsSystemTime(const COleDateTime& dt, SYSTEMTIME& sysTime)
{
	BOOL bRetVal = FALSE;
	if (dt.GetStatus() == COleDateTime::valid)
	{
		struct tm tmTemp;
		if (_AfxTmFromOleDate(dt.m_dt, tmTemp))
		{
			sysTime.wYear = (WORD) tmTemp.tm_year;
			sysTime.wMonth = (WORD) tmTemp.tm_mon;
			sysTime.wDayOfWeek = (WORD) tmTemp.tm_wday;
			sysTime.wDay = (WORD) tmTemp.tm_mday;
			sysTime.wHour = (WORD) tmTemp.tm_hour;
			sysTime.wMinute = (WORD) tmTemp.tm_min;
			sysTime.wSecond = (WORD) tmTemp.tm_sec;
			sysTime.wMilliseconds = 0;

			bRetVal = TRUE;
		}
	}

	return bRetVal;
}
#endif // UNDER_CE < 210

TString ShortDateString(TString format)
{
	/*TString result;
	SYSTEMTIME time;
	GetLocalTime(&time);
	result.Format(_T("%d/%d/%d"), time.wMonth, time.wDay, time.wYear);
	return result;*/
	//COleDateTime dt = COleDateTime::GetCurrentTime();
	TString date;
	if (format.IsEmpty()) {
		LPTSTR buf = date.GetBuffer(128);
		int count = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, buf, 128);
		date.ReleaseBuffer(count > 0 ? count - 1 : 0);
	}
	else {
//#ifdef __WINCE__
		LPTSTR buf = date.GetBuffer(128);
		int count = GetDateFormat(LOCALE_USER_DEFAULT, 0, NULL, format, buf, 128);
		date.ReleaseBuffer(count > 0 ? count - 1 : 0);
		//return dt.Format(VAR_DATEVALUEONLY, LANG_USER_DEFAULT);
//#else
//		return dt.Format((LPCTSTR)format);
//#endif
	}
	return date;
}

TString CurrentTimeString(TString format)
{
	/*TString result;
	SYSTEMTIME time;
	TCHAR ampm[] = _T("AM");

	GetLocalTime(&time);
	if(time.wHour >= 12) {
		_tcscpy(ampm, _T("PM"));
		time.wHour -= 12;
	}
	if(time.wHour == 0 ) {
		// Adjust if midnight hour
		time.wHour = 12;
	}

	result.Format(_T("%d:%02d %s"), time.wHour, time.wMinute, ampm);
	return result;*/
	//COleDateTime dt = COleDateTime::GetCurrentTime();
	//return dt.Format(VAR_TIMEVALUEONLY, LANG_USER_DEFAULT);
	TString theTime;
	int count = 0;
	LPTSTR buf = theTime.GetBuffer(128);
	if (format.IsEmpty()) {
		count = GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, NULL, buf, 128);
	}
	else {
		count = GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, format, buf, 128);
	}
	theTime.ReleaseBuffer(count > 0 ? count - 1 : 0);
	return theTime;

}

TString ShortDateToLongDate(TString date)
{
	COleDateTime t;
	TString result;
	SYSTEMTIME st;
	LPTSTR buf = result.GetBuffer(128);
	int count = 0;
	_TRY {
		date.TrimLeft();
		if (date.IsEmpty()) {
			GetLocalTime(&st);
			count = GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, buf, 128);
		}
#if UNDER_CE < 210
		else if (t.ParseDateTime(date) && __GetAsSystemTime(t, st)) {
#else
		else if (t.ParseDateTime(date) && t.GetAsSystemTime(st)) {
#endif
			count = GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, buf, 128);
		}
	}
	_CATCH_ALL {

	}
	result.ReleaseBuffer(count > 0 ? count - 1 : 0);
	return result;
}
#else // !defined(__WINNT__) && !defined(__WINCE__)
#include <time.h>
TString ShortDateString()
{
	TCHAR buf[64];
	time_t theTime;
	time(&theTime);
	strftime(buf, 63, "%m/%d/%Y", localtime(&theTime));
	return TString(buf);
}

TString CurrentTimeString()
{
	TString result;
	time_t ltime;
	struct tm *today;
	TCHAR ampm[] = _T("AM");

	time(&ltime);
	today = localtime(&ltime);
	if(today->tm_hour >= 12) {
		_tcscpy(ampm, _T("PM"));
		today->tm_hour -= 12;
	}
	if( today->tm_hour == 0 ) {
		// Adjust if midnight hour
		today->tm_hour = 12;
	}

	result.Format(_T("%d:%02d %s"), today->tm_hour, today->tm_min, ampm);
	return result;
}

TString ShortDateToLongDate(TString date)
{
	int idx;
	TString monthstr, daystr;

	if (date.IsEmpty()) {
		// If empty string, return todays date
		date = ShortDateString();
	}

	idx = date.Find(_T("/"));
	monthstr = date.Left(idx);
	date = date.Mid(idx + 1);

	idx = date.Find(_T("/"));
	daystr = date.Left(idx);
	date = date.Mid(idx + 1);	// This remainder is the year

	TString result;
	result.Format(_T("%s %d, %s"), (LPCTSTR)MonthOf(_ttoi(monthstr)), _ttoi(daystr),
		(LPCTSTR)YearOf(_ttoi(date)));
	return result;
}
#endif	// defined(__WINNT__) || defined(__WINCE__)

TString LongDateString()
{
	return ShortDateToLongDate(ShortDateString(_T("")));
}

CORE_FUNC(core_pos)
{
	REQUIRE_ARGS(2);
	REQUIRE_STRING(0);
	REQUIRE_STRING(1);
	CString str1 = args[0].String();
	CString str2 = args[1].String();
	if (str1.IsEmpty() || str2.IsEmpty()) return 0;
	return (int)(str1.Find(str2) + 1);	// Dialect strings 1 based, C are zero based
}

CORE_FUNC(core_time_)
{
	TString format;
	if (argCount > 0) {
		format = args[0].String();
	}
	return CurrentTimeString(format);
}

CORE_FUNC(core_longdate_)
{
	if (argCount == 0) {
		return LongDateString();
	}
	else if (argCount == 1) {
		// If the argument "12/10/97" is passed to longdate$
		// convert it to "Decemeber 10, 1997"
		// ShortDateToLongDate() returns current date if empty string
		return ShortDateToLongDate(args[0].String());
	}
	else {
		ERR("Invalid arg count to longdate$()")
	}
}

CORE_FUNC(core_date_)
{
	TString format;
	if (argCount >= 1) {
		format = args[0].String();
	}
	return ShortDateString(format);
}

CORE_FUNC(core_cap_)
{
	REQUIRE_ARGS(1);
	TString str = args[0].String();
	if (str.GetLength() > 0) {
		TCHAR ch = str[0];
		if ((ch >= 'a') && (ch <= 'z')) {
			if (str.GetLength() == 1) {
				str = _toupper(ch);
			}
			else {
				str.Format(_T("%c%s"), _toupper(ch), (LPCTSTR)str.Mid(1));
			}
			return str;
		}
	}
	return str;
}

CORE_FUNC(core_upper_)
{
	REQUIRE_ARGS(1);
	CString result = args[0].String();
	result.MakeUpper();
	return result;
}

CORE_FUNC(core_lower_)
{
	REQUIRE_ARGS(1);
	CString result = args[0].String();
	result.MakeLower();
	return result;
}

CORE_FUNC(core_getChunkAddr)
{
	if ((argCount != 1) || (args[0].Type() != stChunk)) {
		ERR("GetChunkAddr takes one chunk argument");
	}
	VALUE v;
	return (int)args[0].GetChunkData();
}

///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////

void InitCoreFuncs(CFrameImpl* symtab)
{
#ifndef DIALECT_LITE
	symtab->__set__(_T("compilefile"), AllocFunction(CompileFile, _T("__core__")));
#endif
	symtab->__set__(_T("getchunkaddr"), AllocFunction(core_getChunkAddr, _T("__core__")));
	symtab->__set__(_T("mid$"), AllocFunction(mid_, _T("__core__")));
	symtab->__set__(_T("message"), AllocFunction(message, _T("__core__")));
	symtab->__set__(_T("inputbox"), AllocFunction(inputbox, _T("__core__")));
	symtab->__set__(_T("array"), AllocFunction(array, _T("__core__")));
	symtab->__set__(_T("resize"), AllocFunction(resize, _T("__core__")));
	symtab->__set__(_T("trim$"), AllocFunction(trim_, _T("__core__")));
	symtab->__set__(_T("split"), AllocFunction(split, _T("__core__")));
	symtab->__set__(_T("clone"), AllocFunction(clone, _T("__core__")));
	symtab->__set__(_T("deepclone"), AllocFunction(deepclone, _T("__core__")));
	symtab->__set__(_T("callstack"), AllocFunction(callstack, _T("__core__")));
	symtab->__set__(_T("makeexe"), AllocFunction(makeexe, _T("__core__")));
	symtab->__set__(_T("match"), AllocFunction(match, _T("__core__")));
	symtab->__set__(_T("int"), AllocFunction(_int_, _T("__core__")));
	symtab->__set__(_T("float"), AllocFunction(_float_, _T("__core__")));
	symtab->__set__(_T("asc"), AllocFunction(asc, _T("__core__")));
	symtab->__set__(_T("format"), AllocFunction(format, _T("__core__")));
	symtab->__set__(_T("abs"), AllocFunction(abs, _T("__core__")));
	symtab->__set__(_T("join"), AllocFunction(join, _T("__core__")));
	symtab->__set__(_T("remove"), AllocFunction(remove, _T("__core__")));
	symtab->__set__(_T("unparse"), AllocFunction(unparse, _T("__core__")));
	symtab->__set__(_T("mergesort"), AllocFunction(_mergesort, _T("__core__")));
	symtab->__set__(_T("abortProgram"), AllocFunction(abortProgram, _T("__core__")));

	symtab->__set__(_T("pos"), AllocFunction(core_pos, _T("__core__")));
	symtab->__set__(_T("time$"), AllocFunction(core_time_, _T("__core__")));
	symtab->__set__(_T("longdate$"), AllocFunction(core_longdate_, _T("__core__")));
	symtab->__set__(_T("date$"), AllocFunction(core_date_, _T("__core__")));
	symtab->__set__(_T("upper$"), AllocFunction(core_upper_, _T("__core__")));
	symtab->__set__(_T("lower$"), AllocFunction(core_lower_, _T("__core__")));
	symtab->__set__(_T("cap$"), AllocFunction(core_cap_, _T("__core__")));
}

///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////

#if defined(__WINNT__) || defined(__WINCE__)

#define IDC_INPUTEDIT                   1005
#define IDC_MESSAGEEDIT                 1007

BOOL CALLBACK InputBoxDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

struct InputDlgData {
	TString message;
	TString initValue;
	bool bPassword;
};

static TCHAR buf[512];	// Something strange is going on here...  Hence this for now...

LRESULT DisplayInputBoxDlg(LPARAM lParam);

bool InputBox(TString message, TString initValue, bool bPassword, TString& result)
{
	InputDlgData idd;
	idd.message = message;
	idd.initValue = initValue;
	idd.bPassword = bPassword;

	memset(buf, 0, 512 * sizeof(TCHAR));

	//int i = DialogBoxParam((HINSTANCE)NULL, MAKEINTRESOURCE(IDD_INPUTBOX_DLG), NULL,
	//	(DLGPROC)InputBoxDlgProc, (LPARAM)&idd);
	int i = DisplayInputBoxDlg((LPARAM)&idd);

	if (i == -1) {
		MessageBox(NULL, _T("Failed to create InputBox"), _T("Error"), MB_OK);
	}
	else if (i == IDOK) {
		result = buf;
		return true;
	}

	result.Empty();
	return false;
}

BOOL CALLBACK InputBoxDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	InputDlgData* idd;

	switch (uMsg) {
		case WM_INITDIALOG:
			idd = (InputDlgData*)lParam;
			if (idd->bPassword) {
				SendMessage(GetDlgItem(hwndDlg, IDC_INPUTEDIT), EM_SETPASSWORDCHAR, '*', 0);
			}
			SetWindowText(GetDlgItem(hwndDlg, IDC_INPUTEDIT), idd->initValue);
			SetWindowText(GetDlgItem(hwndDlg, IDC_MESSAGEEDIT), idd->message);
			return FALSE;
		case WM_SIZE:
			//MoveWindow(GetDlgItem(hScreenOutputDlg, IDC_SCREEN_EDIT), 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
			break;
		case WM_CLOSE:
			EndDialog(hwndDlg, IDCANCEL);
			break;
		case WM_COMMAND:
			if (HIWORD(wParam) == BN_CLICKED) {
				GetWindowText(GetDlgItem(hwndDlg, IDC_INPUTEDIT), buf, 512);
				EndDialog(hwndDlg, (int)LOWORD(wParam));
				return FALSE;
			}
			break;
		default:
			return FALSE;
	}
	return TRUE;
}

//====================================================================

static LRESULT DisplayInputBoxDlg(LPARAM lParam)
{
	// The first step is to allocate memory to define the dialog.  The information to be
	// stored in the allocated buffer is the following:
	//
	// 1.  DLGTEMPLATE structure
	// 2.    0x0000 (Word) indicating the dialog has no menu
	// 3.    0x0000 (Word) Let windows assign default class to the dialog
	// 4.    (Caption)  Null terminated unicode string
	// 5.	 0x000B  (size of the font to be used)
	// 6.    "Arial"  (name of the typeface to be used)
	// 7.  DLGITEMTEMPLATE structure for the button	 (HAS TO BE DWORD ALIGNED)
	// 8.	 0x0080  to indicate the control is a button
	// 9.    (Title). Unicode null terminated string with the caption
	// 10.    0x0000   0 extra bytes of data for this control
	// 11.  DLGITEMTEMPLATE structure for the Static Text  (HAS TO BE DWORD ALIGNED)
	// 12.    0x0081 to indicate the control is static text
	// 13.   (Title). Unicode null terminated string with the text
	// 14     0x0000.  0 extra bytes of data for this control
	// 15. DLGITEMTEMPLATE structure for the Edit Control (HAS TO BE DWORD ALIGNED)
	// 16.   0x0082 to indicate an Edit control
	// 17.   (Text) - Null terminated unicode string to appear in the edit control
	// 18.   0x0000. 0 extra bytes of data for this control

	WCHAR szBoxCaption[] = L"Input";
	WCHAR szFontName[] = L"ARIAL";

	// will first convert the control captions to UNICODE
	int	nTotalLength = 0;
	int nBufferSize = 1024;	// Just guess, better too large than too small

	HLOCAL hLocal = LocalAlloc(LHND, nBufferSize);
	if (hLocal == NULL)
		return -1;

	BYTE*	pBuffer = (BYTE*)LocalLock(hLocal);
	if (pBuffer == NULL)
	{
		LocalFree(hLocal);
		return -1;
	}

	DLGTEMPLATE dlgTempl;
	// now initialize the DLGTEMPLATE structure
#ifdef __WINCE__
	dlgTempl.style = WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_SETFONT;
#else
	dlgTempl.style = WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP |
		WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | DS_SETFONT;
#endif
	dlgTempl.dwExtendedStyle = 0;
	dlgTempl.x = 0;
	dlgTempl.y = 0;
	dlgTempl.cx = 250;
	dlgTempl.cy = 95;
	dlgTempl.cdit = 3;  // 3 dialog items in the dialog

	BYTE*	pdest = pBuffer;
	// transfer DLGTEMPLATE structure to the buffer
	memcpy(pdest, &dlgTempl, sizeof(DLGTEMPLATE));
	pdest += sizeof(DLGTEMPLATE);
	*(WORD*)pdest = 0; // no menu
	pdest += sizeof(WORD);
	*(WORD*)pdest = 0;  // use default window class
	pdest += sizeof(WORD);
	memcpy(pdest, szBoxCaption, sizeof(szBoxCaption));
	pdest += sizeof(szBoxCaption);
	*(WORD*)pdest = 8;  // font size
	pdest += sizeof(WORD);
	memcpy(pdest, szFontName, sizeof(szFontName));
	pdest += sizeof(szFontName);

	WCHAR*	pchCaption;
	int	nChars, nActualChars;
	DLGITEMTEMPLATE dlgItemTemp;

	/////////////////////////////////////////////
	/////////////////////////////////////////////
	// Input Edit...
	dlgItemTemp.x  = 4; dlgItemTemp.y  = 78;
	dlgItemTemp.cx = 201; dlgItemTemp.cy = 14;
	dlgItemTemp.id = IDC_INPUTEDIT;
	dlgItemTemp.style = WS_CHILD | ES_AUTOVSCROLL | WS_VISIBLE | WS_BORDER;
	dlgItemTemp.dwExtendedStyle = WS_EX_CLIENTEDGE;

	pdest = (BYTE*)(((DWORD)pdest + 3) & ~3);  // make the pointer DWORD aligned
	memcpy(pdest, (void *)&dlgItemTemp, sizeof(DLGITEMTEMPLATE));

	pdest += sizeof(DLGITEMTEMPLATE);
	*(WORD*)pdest = 0xFFFF;  // indicating atom value
	pdest += sizeof(WORD);
	*(WORD*)pdest = 0x0081;	// atom value for the edit control
	pdest += sizeof(WORD);

	// transfer the caption even when it is an empty string
	nChars = 1;
	pchCaption = new WCHAR[nChars];
	nActualChars = MultiByteToWideChar(CP_ACP, 0, "", -1, pchCaption, nChars);
	memcpy(pdest, pchCaption, nActualChars * sizeof(WCHAR));
	pdest += nActualChars * sizeof(WCHAR);
	delete pchCaption;

	*(WORD*)pdest = 0;  // How many bytes in data for control
	pdest += sizeof(WORD);

	/////////////////////////////////////////////
	/////////////////////////////////////////////
	// OK pushbutton...
	dlgItemTemp.x  = 210; dlgItemTemp.y  = 78;
	dlgItemTemp.cx = 35; dlgItemTemp.cy = 14;
	dlgItemTemp.id = IDOK;  // OK button identifier
	dlgItemTemp.style = WS_CHILD | BS_DEFPUSHBUTTON | WS_VISIBLE;
	dlgItemTemp.dwExtendedStyle = 0;

	pdest = (BYTE*)(((DWORD)pdest + 3) & ~3);  // make the pointer DWORD aligned
	memcpy(pdest, (void *)&dlgItemTemp, sizeof(DLGITEMTEMPLATE));

	pdest += sizeof(DLGITEMTEMPLATE);
	*(WORD*)pdest = 0xFFFF;  // indicating atom value
	pdest += sizeof(WORD);
	*(WORD*)pdest = 0x0080;	// atom value for the button control
	pdest += sizeof(WORD);

	// transfer the caption even when it is an empty string
	nChars = 3;
	pchCaption = new WCHAR[nChars];
	nActualChars = MultiByteToWideChar(CP_ACP, 0, "OK", -1, pchCaption, nChars);
	memcpy(pdest, pchCaption, nActualChars * sizeof(WCHAR));
	pdest += nActualChars * sizeof(WCHAR);
	delete pchCaption;

	*(WORD*)pdest = 0;  // How many bytes in data for control
	pdest += sizeof(WORD);

	/////////////////////////////////////////////
	/////////////////////////////////////////////
	// Message Edit...
	dlgItemTemp.x  = 4; dlgItemTemp.y  = 3;
	dlgItemTemp.cx = 240; dlgItemTemp.cy = 73;
	dlgItemTemp.id = IDC_MESSAGEEDIT;
	dlgItemTemp.style = WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL |
		ES_READONLY | WS_VSCROLL | WS_BORDER;
	dlgItemTemp.dwExtendedStyle = WS_EX_CLIENTEDGE;

	pdest = (BYTE*)(((DWORD)pdest + 3) & ~3);  // make the pointer DWORD aligned
	memcpy(pdest, (void *)&dlgItemTemp, sizeof(DLGITEMTEMPLATE));

	pdest += sizeof(DLGITEMTEMPLATE);
	*(WORD*)pdest = 0xFFFF;  // indicating atom value
	pdest += sizeof(WORD);
	*(WORD*)pdest = 0x0081;	// atom value for the edit control
	pdest += sizeof(WORD);

	// transfer the caption even when it is an empty string
	nChars = 1;
	pchCaption = new WCHAR[nChars];
	nActualChars = MultiByteToWideChar(CP_ACP, 0, "", -1, pchCaption, nChars);
	memcpy(pdest, pchCaption, nActualChars * sizeof(WCHAR));
	pdest += nActualChars * sizeof(WCHAR);
	delete pchCaption;

	*(WORD*)pdest = 0;  // How many bytes in data for control
	pdest += sizeof(WORD);

	/////////////////////////////////////////////
	/////////////////////////////////////////////

	LRESULT result = DialogBoxIndirectParam(NULL, (DLGTEMPLATE*)pBuffer, NULL,
		(DLGPROC)InputBoxDlgProc, lParam);

	LocalUnlock(hLocal);
	LocalFree(hLocal);

	return result;
}

CORE_FUNC(message)
{
	if (argCount != 1) {
		pInterp->SyntaxError(_T("message() requires one argument"));
		return AllocNil();
	}
//#ifdef USING_MFC
//	AfxMessageBox(args[0].String());
//#else
	MessageBox(NULL, args[0].String(), _T(""), MB_OK);
//#endif
	return AllocNil();
}

CORE_FUNC(inputbox)
{
	if ((argCount <= 0) || (argCount > 3)) {
		pInterp->SyntaxError(_T("inputbox takes one to three args <message, initValue, IsPassword>"));
		return AllocNil();
	}

	TString message = args[0].String();
	TString initValue;
	bool bPassword = false;

	if (argCount >= 2) initValue = args[1].String();
	if (argCount == 3) bPassword = args[2].Boolean();

	TString result;
	if (InputBox(message, initValue, bPassword, result)) {
		return result;
	}
	else {
		return AllocNil();
	}
}

#else

static VALUE message(VALUE* self, int argCount, VALUE args[], CInterpreter* pInterp)
{
	if (argCount != 1) {
		pInterp->SyntaxError(_T("message() requires one argument"));
		return AllocNil();
	}
	printf(_T("%s\n"), args[0].String());
	return AllocNil();
}

static VALUE inputbox(VALUE* self, int argCount, VALUE args[], CInterpreter* pInterp)
{
	if ((argCount <= 0) || (argCount > 3)) {
		pInterp->SyntaxError(_T("inputbox takes one to three args <message, initValue, IsPassword>"));
		return AllocNil();
	}

	TString message = args[0].String();
	TString initValue;
	bool bPassword = false;

	if (argCount >= 2) initValue = args[1].String();
	if (argCount == 3) bPassword = args[2].Boolean();

	TCHAR result[512];
	_tprintf(_T("%s> "), (LPCTSTR)message);
	_tscanf(_T("%s"), result);
	return result;
}

#endif // defined(__WINNT__) || defined(__WINCE__)
